package yhao.demo.service.erp.workflow.service.task;

import org.apache.commons.lang.StringUtils;
import yhao.demo.service.erp.workflow.apilist.form.task.*;
import yhao.infra.apilist.validate.IdForm;
import yhao.infra.common.util.CommonDateUtil;
import yhao.infra.common.util.CommonListUtil;
import yhao.demo.service.erp.workflow.apilist.enums.flow.FlowNodeAssignType;
import yhao.demo.service.erp.workflow.apilist.enums.task.TaskStatus;
import yhao.demo.service.erp.workflow.apilist.form.flow.FlowNodeItemQueryForm;
import yhao.demo.service.erp.workflow.apilist.form.flow.FlowNodeNotifyQueryForm;
import yhao.demo.service.erp.workflow.apilist.model.flow.FlowNodeItemModel;
import yhao.demo.service.erp.workflow.apilist.model.flow.FlowNodeModel;
import yhao.demo.service.erp.workflow.apilist.model.flow.FlowProcessDefinitionModel;
import yhao.demo.service.erp.workflow.apilist.model.task.TaskModel;
import yhao.demo.service.erp.workflow.apilist.model.task.TaskNodeItemModel;
import yhao.demo.service.erp.workflow.apilist.model.task.TaskNodeNotifyModel;
import yhao.demo.service.erp.workflow.apilist.model.task.TaskNodeWarnModel;
import yhao.demo.service.erp.workflow.dao.TaskDao;
import yhao.demo.service.erp.workflow.service.pvm.ProcessSearcher;
import yhao.demo.service.erp.workflow.service.task.chain.EndPoint;

import java.util.*;

/**
 * 任务执行器
 */
public abstract class TaskCommand{
    protected TaskDao taskDao;
    protected ProcessSearcher processSearcher;
    protected TaskContext context;
    private List<EndPoint> pointList;

    public TaskCommand(TaskDao taskDao, ProcessSearcher processSearcher) {
        this.taskDao = taskDao;
        this.processSearcher = processSearcher;
        this.context =  new TaskContext();
        initChain();
    }

    private void doChain(TaskContext context) {
        if(pointList.size()>0){
            for(EndPoint point:pointList){
                point.doPoint(context);
            }
        }
    }

    protected abstract void initContext();

    protected abstract Object command();

    private void initChain(){
        pointList = new ArrayList<>();
        //执行历史记录录入
        pointList.add(ctx -> {
            if(ctx.getTask()!=null && ctx.getCurrentNode()!=null){
                TaskHistoryForm form = new TaskHistoryForm();
                form.setTaskId(ctx.getTask().getId());
                form.setTaskNodeId(ctx.getCurrentNode().getId());
                form.setHistory(ctx.getHistory().toString());
                taskDao.insertHistory(form);
            }
        });
        //通知节点
        pointList.add(ctx -> {
            if(ctx.getNextNode()!=null){
                FlowNodeNotifyQueryForm form = new FlowNodeNotifyQueryForm();
                form.setNodeId(ctx.getNextNode().getId());
                List<TaskNodeNotifyModel> personList = taskDao.queryTaskNodeNotify(form);
                //taskDao.notify(); TODO 做具体的通知动作
            }
        });
    }

    /**
     * 执行任务
     * @return
     */
    public Object doCommand(){
        initContext();
        Object result = command();
        doChain(this.context);
        return result;
    }

    /**
     * 初始化任务的上下文通用数据
     * @param form
     */
    protected void initDealTask(IdForm form){
        TaskModel task = taskDao.pickTaskById(form.getId());
        if(task==null){
            throw new RuntimeException("任务Id有误，请确认数据");
        }
        FlowNodeModel currentNode = processSearcher.findCurrentNode(task.getProcessId(),task.getCurrentNodeId());
        if(currentNode==null){
            throw new RuntimeException("任务关联流程定义有误，请确认数据");
        }
        this.context.setTask(task);
        this.context.setCurrentNode(currentNode);
    }

    /**
     * 操作更新任务的节点事项
     * @param itemList
     */
    protected void updateTaskNodeItem(List<TaskNodeItemForm> itemList) {
        if(!CommonListUtil.isEmpty(itemList)){
            addHistory(1,"[更新事项]","");
            for(TaskNodeItemForm item:itemList){
                taskDao.updateNodeItemResult(item);
                addHistory(2,"[更新事项]",item.getItemName()+":"+item.getResult());
            }
        }
    }

    /**判断是否启动子流程任务
     * **/
    protected boolean initSubTask(TaskModel task,FlowNodeModel node){
        if(StringUtils.isNotEmpty(node.getSubProcessId())){
            FlowProcessDefinitionModel subDef = processSearcher.searchProcessDefinitionById(node.getSubProcessId());
            TaskStartForm form = new TaskStartForm();
            form.setBizCodeStr(subDef.getBizCode());
            form.setBizEntityId(task.getBizEntityId());
            form.setParentId(task.getId());
            form.setOperatorId(task.getUpdateOperatorId());
            FlowNodeModel subStartNode = processSearcher.findStartNode(subDef.getBizCode());
            startTask(form,subStartNode);

            return true;
        }
        return false;
    }

    /**
     * 开启任务
     * @param form
     * @param nextNode
     */
    protected void startTask(TaskStartForm form,FlowNodeModel nextNode) {
        form.setId(UUID.randomUUID().toString());
        //查找下一节点执行人
        form.setNodeId(nextNode.getId());
        form.setProcessId(nextNode.getProcessDefinitionId());
        form.setAssignType(nextNode.getAssignType());
        form.setAssignEntityId(nextNode.getAssignEntityId());
        TaskModel task = new TaskModel();
        task.setId(form.getId());
        task.setBizEntityId(form.getBizEntityId());
        task.setCreateOperatorId(form.getOperatorId());
        task.setUpdateOperatorId(form.getOperatorId());
        if(initSubTask(task,nextNode)){
            form.setStatus(TaskStatus.SUB_TASK);
        }
        //保存任务
        taskDao.startNewTask(form);

        //生成节点事项
        initTaskNodeItem(form.getId(),nextNode);
        //生成节点预警
        resetTaskNodeWarn(form.getId(),nextNode);
    }

    /**
     * 初始化任务的节点事项
     * @param taskId
     * @param node
     */
    protected void initTaskNodeItem(String taskId,  FlowNodeModel node) {
        FlowNodeItemQueryForm form = new FlowNodeItemQueryForm();
        form.setNodeId(node.getId());
        if(!CommonListUtil.isEmpty(node.getItems())){
            addHistory(1,"[新建事项]","");
            List<TaskNodeItemModel> taskNodes = new ArrayList<>();
            for(FlowNodeItemModel flowModel :node.getItems()){
                TaskNodeItemModel nodeItem = new TaskNodeItemModel();
                nodeItem.setTaskId(taskId);
                nodeItem.setTaskNodeId(node.getId());
                nodeItem.setItemConfig(flowModel.getItemConfig());
                nodeItem.setItemExcept(flowModel.getItemExcept());
                nodeItem.setItemExceptType(flowModel.getItemExceptType());
                nodeItem.setItemName(flowModel.getItemName());
                nodeItem.setNecessary(flowModel.isNecessary());
                taskNodes.add(nodeItem);
                addHistory(2,"[新建事项]",flowModel.getItemName());
            }
            taskDao.insertTaskNodeItem(taskNodes);
        }
    }

    /**
     * 从节点列表中获得指定id的节点信息
     * @param nextNodeId
     * @param nodeList
     * @return
     */
    protected FlowNodeModel pickNextNode(String nextNodeId, List<FlowNodeModel> nodeList) {
        if(StringUtils.isEmpty(nextNodeId)){
            throw new RuntimeException("非终结节点必须选择下一节点id");
        }
        FlowNodeModel nextNode = null;
        for(FlowNodeModel node:nodeList){
            if(node.getId().equals(nextNodeId)){
                nextNode = node;
            }
        }
        if(nextNode==null){
            throw new RuntimeException("下一节点id设置有误");
        }
        return nextNode;
    }

    /**
     * 添加操作历史记录
     * @param level
     * @param title
     * @param info
     */
    protected void addHistory(int level,String title,String info){
        context.getHistory().append("├─");
        while(level>1){
            context.getHistory().append("├─");
            level--;
        }
        context.getHistory().append(title)
                .append(":").append(info).append("\r\n");
    }

    /**
     * 关闭节点预警
     * @param taskId
     * @param currentNode
     */
    protected void closeTaskNodeWarn(String taskId, FlowNodeModel currentNode){
        TaskNodeWarnCloseForm form = new TaskNodeWarnCloseForm();
        form.setTaskId(taskId);
        form.setNodeId(currentNode.getId());
        form.setEndTime(new Date());
        taskDao.closeTaskNodeWarn(form);
    }

    /**
     * 重置预警节点
     * @param taskId
     * @param nextNode
     */
    protected void resetTaskNodeWarn(String taskId, FlowNodeModel nextNode) {
        if(nextNode.getWarn()){
            TaskNodeWarnBaseForm query = new TaskNodeWarnBaseForm();
            query.setTaskId(taskId);
            query.setNodeId(nextNode.getId());
            TaskNodeWarnModel warnModel = taskDao.selectTaskNodeWarn(query);
            Date now = new Date();
            if(warnModel==null){
                warnModel = new TaskNodeWarnModel();
                warnModel.setId(UUID.randomUUID().toString());
                warnModel.setTaskId(taskId);
                warnModel.setNodeId(nextNode.getId());
                warnModel.setStartTime(now);
                warnModel.setUpdateTime(now);
                if(nextNode.getWarn()){
                    if(nextNode.getNodeWarn().getLimit()!=null){
                        warnModel.setLimitEndTime(CommonDateUtil.addHour(now,nextNode.getNodeWarn().getLimit()));
                        if(nextNode.getNodeWarn().getLimitWarn()!=null &&
                                nextNode.getNodeWarn().getLimit()>nextNode.getNodeWarn().getLimitWarn()){
                            warnModel.setLimitWarnTime(CommonDateUtil.addHour(now,
                                    nextNode.getNodeWarn().getLimit()-nextNode.getNodeWarn().getLimitWarn()));
                        }
                        if(nextNode.getNodeWarn().getLevelOneWarn()!=null){
                            warnModel.setLevelOneTime(CommonDateUtil.addHour(now,
                                    nextNode.getNodeWarn().getLimit()+nextNode.getNodeWarn().getLevelOneWarn()));
                        }
                        if(nextNode.getNodeWarn().getLevelTwoWarn()!=null){
                            warnModel.setLevelTwoTime(CommonDateUtil.addHour(now,
                                    nextNode.getNodeWarn().getLimit()+nextNode.getNodeWarn().getLevelTwoWarn()));
                        }
                        if(nextNode.getNodeWarn().getLevelThreeWarn()!=null){
                            warnModel.setLevelThreeTime(CommonDateUtil.addHour(now,
                                    nextNode.getNodeWarn().getLimit()+nextNode.getNodeWarn().getLevelThreeWarn()));
                        }
                    }
                }
                taskDao.insertTaskNodeWarn(warnModel);
            }else{
                warnModel.setUpdateTime(now);
                taskDao.resetTaskNodeWarn(warnModel);
            }
        }
    }

    /**
     * 检查是否存在未完成的子任务
     * @param parentTaskId
     */
    protected void checkSubTask(String parentTaskId) {
        List<TaskModel> taskList = taskDao.queryTaskByParentId(parentTaskId);
        for(TaskModel subTask:taskList){
            if(subTask.getStatus()==TaskStatus.RUNNING||
                    subTask.getStatus()==TaskStatus.SUB_TASK){
                throw new RuntimeException("子任务未完成，不能执行通过/驳回操作。");
            }
        }
    }

    /**
     * 检查操作人权限
     * @param task
     * @param form
     * @return
     */
    protected void checkAssign(TaskModel task, TaskDealForm form) {
        boolean result;
        if(task.getAssignType()== FlowNodeAssignType.PERSON){
            result = task.getAssignEntityId().equals(form.getOperatorId());
        }else{
            result = task.getAssignEntityId().equals(form.getOperatorOrgId());
        }
        if(!result){
            throw new RuntimeException("操作人权限不足。");
        }
    }

    /**
     * 检查任务有效性
     * @param task
     */
    protected void checkTask(TaskModel task) {
        if(!TaskStatus.RUNNING.equals(task.getStatus())){
            throw new RuntimeException("任务当前状态不可操作："+task.getStatus());
        }
    }

    /**
     * 新增自定义处理
     * @param point
     */
    public void addEndPoint(EndPoint point){
        this.pointList.add(point);
    }
}
